#! -*- coding:utf-8 -*-
import fcntl
import json
import logging
import random
import sys

from alipay.exceptions import AliPayException
from django.core.management.base import BaseCommand

from async import async_job
from common.cache import redis_cache
from common.cache.redis_cache import get_available_withdraw_channels
from common.transaction import calc_withdraw_amount
from common.transaction.db import withdraw_back, CAN_BE_BACK_STATUS
from common.transaction.model import (WITHDRAW_TYPE, WITHDRAW_STATUS,
                                      Withdraw, WITHDRAW_CHANNELS, CHECK_STATUS)
from common.utils import tz
from common.utils.exceptions import DbError, DataError
from common.withdraw import check_user_risk, check_daily_risk, notify_balance, get_available_withdraw_type
from common.withdraw.group_withdraw_new import group_withdraw_new_pay
from common.withdraw.just_withdraw import create_order as just_withdraw_pay
from common.withdraw.just_withdraw import query_withdraw_channels
from common.withdraw.unionagency_withdraw import create_charge

_LOGGER = logging.getLogger('alipay')

_BLACK_LIST = {
    'zfb': ['15518854513', '15230115813', '18059353384', '15614159249', '13960629129', '15137261823', '15050312853',
            '18862223727'],
    'name': []
}

_SYSTEM_ADMIN_ID = -1


def back_disabled_orders():
    """
    对当前关闭的提现方式自动返款，未通过审核的订单需人工返款
    """
    items = Withdraw.query.filter(Withdraw.check_status == CHECK_STATUS.NULL). \
        filter(Withdraw.status.in_(CAN_BE_BACK_STATUS)). \
        filter(Withdraw.target_type.notin_(get_available_withdraw_type())).all()
    withdraw_ids = []
    for item in items:
        withdraw_ids.append(item.id)
    for withdraw_id in withdraw_ids:
        try:
            withdraw_back(_SYSTEM_ADMIN_ID, withdraw_id, u'上游通道故障，提现失败返款')
            async_job.stats_withdraw.delay([withdraw_id])
        except DbError:
            print 'withdraw status invalid, withdraw_id: %s' % withdraw_id


def check_wait_order():
    items = Withdraw.query.filter(
        Withdraw.status == WITHDRAW_STATUS.WAIT).filter(
        Withdraw.check_status == CHECK_STATUS.NULL).filter(
        Withdraw.target_type.in_(get_available_withdraw_type())).all()
    for item in items:
        uid = item.user_id
        _LOGGER.info('check_wait_order, ready to check id:%s, uid:%s, price:%s',
                     item.id, uid, item.price)
        try:
            succ, reason = check_user_risk(uid, item)
        except Exception as e:
            _LOGGER.exception('check_user_risk error, %s', e)
            continue
        if 'invalid_bankcard' in reason:
            withdraw_back(_SYSTEM_ADMIN_ID, item.id, u'银行卡号异常')
            async_job.stats_withdraw.delay([item.id])
            continue
        if not succ:
            _LOGGER.warn(u'check_wait_order, id %s uid %s in risk, reason:%s skip it!',
                         item.id, uid, reason)
            updated_info = json.loads(item.extend)
            updated_info.update({'auto_risk': reason, 'incheck_time': tz.local_now().strftime('%Y-%m-%d %H:%M:%S')})
            item.extend = json.dumps(updated_info, ensure_ascii=False)
            item.check_status = CHECK_STATUS.WAITING
            item.save()
            continue
        updated_info = json.loads(item.extend)
        updated_info.update({'incheck_time': tz.local_now().strftime('%Y-%m-%d %H:%M:%S')})
        item.extend = json.dumps(updated_info, ensure_ascii=False)
        item.status = WITHDRAW_STATUS.AUTO
        item.save()
        _LOGGER.info('check_wait_order, check id:%s succ', item.id)


def get_available_alipay_chns():
    alipay_chns = []
    av_chns = get_available_withdraw_channels()
    for av_chn in av_chns:
        if av_chn in (WITHDRAW_CHANNELS.JUSTPAY_ALIPAY, WITHDRAW_CHANNELS.GROUP_ALIPAY):
            alipay_chns.append(av_chn)
    return alipay_chns


def pay(order, amount, alipay_num, alipay_name, chosed_context):
    oid = order.id
    trans_info = {}
    try:
        c = query_withdraw_channels()
        just_pay_enable = True if len(c['list']) > 0 else False
    except Exception as e:
        _LOGGER.exception('query justpay withdraw channels error')
        just_pay_enable = False
    alipay_chns = get_available_alipay_chns()
    if not alipay_chns:
        raise DataError('no available alipay chns')
    selected_channel = random.choice(alipay_chns)
    if (selected_channel == WITHDRAW_CHANNELS.JUSTPAY_ALIPAY and
            just_pay_enable):
        # justpay下分
        _LOGGER.info('ready to pay by just, order %s', oid)
        sts = WITHDRAW_STATUS.SUBMIT_TO_THIRD
        just_withdraw_pay(order.id, alipay_num, alipay_name, amount, order.user_id)
        trans_info.update({
            'payer_no': 'justpay',
            'code': 'submit',
            'amount': amount,
            'order_id': oid,
            'out_biz_no': '',
            'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
        })
    if selected_channel == WITHDRAW_CHANNELS.GROUP_ALIPAY:
        # 万豪下分
        _LOGGER.info('ready to pay by group, order %s', oid)
        sts = WITHDRAW_STATUS.SUBMIT_TO_THIRD
        group_withdraw_new_pay(order, amount, alipay_num, alipay_name)
        trans_info.update({
            'payer_no': 'group_new_withdraw',
            'code': 'submit',
            'amount': amount,
            'order_id': oid,
            'out_biz_no': '',
            'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
        })

    return sts, trans_info


def apply_unionagency_withdraw_wait_order():
    items = Withdraw.query.filter(
        Withdraw.target_type == WITHDRAW_TYPE.BANKCARD).filter(
        Withdraw.status == WITHDRAW_STATUS.AUTO).all()
    _LOGGER.info('Auto bankcard withdraw, fetch %d withdraw items', len(items))
    for item in items:
        amount = calc_withdraw_amount(item.user_id, int(item.price))
        item.real_price = amount
        item.save()
        try:
            res = create_charge(item.user_id, item)
            _LOGGER.info('create_charge results: %s', res)
        except Exception as e:
            _LOGGER.exception('bankcard submit to third exception')



def start():
    # change status from 1 to 8
    check_wait_order()

    back_disabled_orders()
    # do withdraw for status 8
    items = Withdraw.query.filter(
        Withdraw.target_type == WITHDRAW_TYPE.ALIPAY).filter(
        Withdraw.status == WITHDRAW_STATUS.AUTO).all()
    _LOGGER.info('Auto trans alipay, fetch %d withdraw items', len(items))
    for item in items:
        item = Withdraw.query.filter(Withdraw.id == item.id).filter(
            Withdraw.status == WITHDRAW_STATUS.AUTO).with_lockmode('update').first()
        if not item:
            continue
        updated_info = json.loads(item.extend)
        trans_info = {}
        alipay_num = updated_info['info'].get('zfb','')
        alipay_name = updated_info['info'].get('name', '')
        # trans api
        sts = WITHDRAW_STATUS.AUTO
        chosed_context = dict()
        try:
            amount = calc_withdraw_amount(item.user_id, int(item.price))
            if alipay_num in _BLACK_LIST['zfb'] or alipay_name in _BLACK_LIST['name']:
                _LOGGER.warn('someone %s withdraw %s refused,zfb:%s, name:%s!', item.user_id, amount, alipay_num,
                             alipay_name)
                item.status = WITHDRAW_STATUS.FORBIDDEN
                item.save()
                continue
            if amount <= 50000:
                sts, trans_info = pay(item, amount, alipay_num, alipay_name, chosed_context)
                async_job.stats_withdraw.delay([item.id])
            else:
                sts = WITHDRAW_STATUS.FAIL
                trans_info.update({
                    'amount': amount,
                    'code': 'EXCEED_LIMIT_PERSONAL_SM_AMOUNT',
                    'sub_code': 'EXCEED_LIMIT_PERSONAL_SM_AMOUNT',
                    'sub_msg': u'转账给个人支付宝账户单笔最多5万元',
                    'out_biz_no': item.id,
                    'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
                })
                _LOGGER.info(u'提现金额单笔需要小于五万, 当前金额 %s, 订单号 %s', amount, item.id)
        except AliPayException as e:
            if e.get_sub_code() == 'PAYER_BALANCE_NOT_ENOUGH':
                sts = WITHDRAW_STATUS.AUTO
                # notify
                notify_balance(chosed_context['payer'])
            else:
                sts = WITHDRAW_STATUS.FAIL
            trans_info.update({
                'amount': amount,
                'code': e.get_code(),
                'sub_code': e.get_sub_code(),
                'sub_msg': e.get_msg(),
                'out_biz_no': item.id,
                'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
            })
            _LOGGER.info(u'AlipayException, {} {} {} {}'.format(item.id, e.get_code(), e.get_sub_code(), e.get_msg()))
        except Exception as e:
            if 'User request too frequently' in str(e) or \
                    ('HTTPSConnectionPool' in str(e) and 'trans.9127pay.com' in str(e) ) \
                    or ('HTTPConnectionPool' in str(e) and '183.60.211.195' in str(e) ) :
                sts = WITHDRAW_STATUS.AUTO
            else:
                sts = WITHDRAW_STATUS.FAIL
            trans_info.update({
                'amount': amount,
                'code': '-1',
                'sub_code': '-1',
                'sub_msg': u'unexpected exception:%s' % e,
                'pay_date': tz.local_now().strftime('%Y-%m-%d %H:%M:%S'),
            })
            _LOGGER.info(u'Unexpected Exception, %s, %s', item.id, e)

        item.status = sts
        updated_info.update({'auto_trans_info': trans_info})
        item.extend = json.dumps(updated_info, ensure_ascii=False)
        item.save()
        if item.status == WITHDRAW_STATUS.DONE:
            check_daily_risk(amount)
            redis_cache.set_withdraw_status(chosed_context['payer']['no'], 0)

    apply_unionagency_withdraw_wait_order()


class Command(BaseCommand):
    def handle(self, **kwargs):
        try:
            f = open('/tmp/flock_%s' % sys.argv[0].split('/')[-1], 'w')
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
            start()
            fcntl.flock(f, fcntl.LOCK_UN)
        except Exception as e:
            _LOGGER.exception('trans error')
            raise e
